Skip to content

10 Docker 基础 - 自定义镜像

在使用 Docker 时,我们经常会使用到镜像来部署应用程序。Docker 提供了丰富的官方镜像,但有时候我们需要根据特定需求来定制自己的镜像。

创建 Dockerfile

Overview of best practices for writing Dockerfiles | Docker Docs

首先,我们需要创建一个 Dockerfile 文件来定义我们的自定义镜像。Dockerfile 中包含了构建镜像的各个步骤。

dockerfile
# 使用官方 ubuntu 镜像作为基础镜像
FROM ubuntu:latest

# 设置镜像的维护者信息
MAINTAINER Your Name <your@email.com>

# 执行更新和安装所需软件
RUN apt-get update && apt-get install -y \
    software-properties-common \
    python3 \
    python3-pip

# 在镜像中创建一个新目录
RUN mkdir -p /myapp

# 设置工作目录
WORKDIR /myapp

# 将本地的 app 文件复制到镜像的 /myapp 目录中
COPY app /myapp

# 设置环境变量
ENV APP_ENV=production

# 暴露容器的端口
EXPOSE 8080

# 配置容器启动后执行的命令
CMD ["python3", "app.py"]

上面的 Dockerfile 中包含了几个常用的指令:

  • FROM 指定了基础镜像:使用了最新版的 Ubuntu 作为基础镜像。
  • MAINTAINER 设置了镜像的维护者信息。
  • RUN 在镜像中执行命令:更新并安装了所需的软件包。
  • WORKDIR 设置工作目录。
  • COPY 将本地文件复制到镜像中。
  • WORKDIR 设置工作目录。
  • ENV 定义了环境变量:环境变量 APP_ENV
  • EXPOSE 暴露容器内部端口:8080。
  • CMD 配置容器启动后执行的命令。

构建镜像

在创建好 Dockerfile 后,我们可以使用 docker build 命令来构建镜像。

bash
docker build -t custom_image:latest .
  • -t custom_image:latest 参数指定指定镜像名称和标签。
  • . 表示 Dockerfile 所在的目录。

使用自定义镜像

构建好镜像后,我们就可以使用它来创建容器了。

bash
docker run -d -p 8080:8080 --name custom_container custom_image:latest
  • -d 参数表示在后台运行容器。
  • -p 8080:8080 指定了端口映射,将容器的 8080 端口映射到宿主机的 8080 端口。
  • --name custom_container 指定了容器的名称。
  • custom_image 是我们构建的自定义镜像的名称。

Dockerfile

FROM 选择基础镜像

  • 选择合适的基础镜像是构建自定义 Docker 镜像的第一步。常见的选择包括 Alpine、Ubuntu、CentOS 等。

  • scratch 是一个特殊的基础镜像,它实际上是一个空白镜像,不包含任何文件系统内容。因此,使用 scratch 作为基础镜像意味着你从头开始构建你的镜像,而不是基于其他镜像。

  • Alpine Linux 是一个轻量级的基础镜像,对于容器来说是一个理想的选择,因为它非常小巧。镜像特点如下:

    • 轻量级: Alpine Linux 以小巧、高效而著称,它的 基础镜像 只有几兆大小(目前小于 6 MB),同时仍是一个完整的 Linux 发行版。这使得构建和部署容器时能够更快地完成。
    • 安全性: Alpine Linux 采用 musl libc 库和 BusyBox 工具集,这两者都是针对安全性和小型化设计的。这使得镜像更加精简,减少了攻击面,提高了容器的安全性。
    • 包管理: Alpine 使用 apk 包管理工具,它简单而高效。你可以轻松地在容器中安装、更新或删除软件包。这种简单性使得容器的构建和维护更为方便。
    • 快速启动: 由于镜像小巧,Alpine 容器的启动速度相对较快。这在微服务架构中尤为重要,因为容器的快速启动能够更好地应对动态负载。
    • 适用于容器: Alpine Linux 被设计用于嵌入式环境和容器化应用。它的设计目标之一是在容器中运行良好,与容器技术(如 Docker)的集成性较好。
dockerfile
# 使用 Alpine Linux 作为基础镜像
FROM alpine:latest

# 安装应用程序或服务
RUN apk --no-cache add \
    nginx

# 配置应用程序或服务
COPY nginx.conf /etc/nginx/nginx.conf

# 定义容器启动时执行的命令
CMD ["nginx", "-g", "daemon off;"]
相关文件
dockerfile
FROM nginx:1.25.3-alpine

CMD ["/bin/bash"]

MAINTAINER "Ryanjie" <54110@qq.com>

ENV TERM=xterm-256color LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LC_ALL=en_US.UTF-8 TZ=Asia/Shanghai

WORKDIR /usr/share/nginx/html

# Copy static assets
COPY ./dist /usr/share/nginx/html

# Copy the nginx configuration file
COPY ./nginx.conf /etc/nginx/nginx.conf

# Expose port 80
EXPOSE 80

# Start nginx
CMD ["nginx", "-g", "daemon off;"]
dockerfile
FROM nginx:1.25.3

CMD ["/bin/bash"]

MAINTAINER "Ryanjie" <54110@qq.com>

ENV TERM=xterm-256color LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LC_ALL=en_US.UTF-8 TZ=Asia/Shanghai

WORKDIR /usr/share/nginx/html

# Copy static assets
COPY ./dist /usr/share/nginx/html

# Copy the nginx configuration file
COPY ./nginx.conf /etc/nginx/nginx.conf

# Expose port 80
EXPOSE 80

# Start nginx
CMD ["nginx", "-g", "daemon off;"]
bash
 tree
.
├── dist
│   └── index.html
├── Dockerfile-nginx-debian
├── Dockerfile-nginx-alpine
└── nginx.conf

1 directory, 4 files
 docker build -t nginx:debian -f Dockerfile-nginx-debian .
[+] Building 0.9s (8/8) FINISHED                                                                                      docker:default
 => [internal] load build definition from Dockerfile-nginx                                                                      0.0s
 => => transferring dockerfile: 431B                                                                                            0.0s
 => [internal] load .dockerignore                                                                                               0.0s
 => => transferring context: 2B                                                                                                 0.0s
 => [internal] load metadata for docker.io/library/nginx:1.25.3                                                                 0.7s
 => [1/3] FROM docker.io/library/nginx:1.25.3@sha256:86e53c4c16a6a276b204b0fd3a8143d86547c967dc8258b3d47c3a21bb68d3c6           0.0s
 => [internal] load build context                                                                                               0.0s
 => => transferring context: 91B                                                                                                0.0s
 => CACHED [2/3] COPY ./dist /usr/share/nginx/html                                                                              0.0s
 => CACHED [3/3] COPY ./nginx.conf /etc/nginx/nginx.conf                                                                        0.0s
 => exporting to image                                                                                                          0.0s
 => => exporting layers                                                                                                         0.0s
 => => writing image sha256:f66dceae84527fe40b91b3df15e4fdc6b0d7328df34e884f37d5193648e03863                                    0.0s
 => => naming to docker.io/library/nginx:debian                                                                                 0.0s
 docker build -t nginx:alpine -f Dockerfile-nginx-alpine .
[+] Building 0.8s (8/8) FINISHED                                                                                      docker:default
 => [internal] load build definition from Dockerfile-nginx-alpine                                                               0.0s
 => => transferring dockerfile: 444B                                                                                            0.0s
 => [internal] load .dockerignore                                                                                               0.0s
 => => transferring context: 2B                                                                                                 0.0s
 => [internal] load metadata for docker.io/library/nginx:1.25.3-alpine                                                          0.6s
 => [1/3] FROM docker.io/library/nginx:1.25.3-alpine@sha256:db353d0f0c479c91bd15e01fc68ed0f33d9c4c52f3415e63332c3d0bf7a4bb77    0.0s
 => [internal] load build context                                                                                               0.0s
 => => transferring context: 91B                                                                                                0.0s
 => CACHED [2/3] COPY ./dist /usr/share/nginx/html                                                                              0.0s
 => CACHED [3/3] COPY ./nginx.conf /etc/nginx/nginx.conf                                                                        0.0s
 => exporting to image                                                                                                          0.0s
 => => exporting layers                                                                                                         0.0s
 => => writing image sha256:80daa91ac6a5fade97b31062f170ccffdcf47239002a4300a61e0b3da19442fe                                    0.0s
 => => naming to docker.io/library/nginx:alpine                                                                                 0.0s
 docker images # 可以看到 alpine 版本的 nginx 镜像比 debian 版本的 nginx 镜像小很多
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
nginx        alpine    80daa91ac6a5   7 seconds ago    47.7MB
nginx        debian    f66dceae8452   35 seconds ago   187MB

多阶段构建

Multi-stage builds | Docker Docs

  • Docker 多阶段构建(Multi-Stage Builds)是一种技术,它允许在一个 Dockerfile 中定义多个构建阶段,每个阶段用于执行特定任务,并最终只将必要的文件从前一阶段复制到最终的镜像中。
  • 这有助于减小最终构建的镜像大小,同时保持构建过程的可读性和维护性。
  • 多阶段构建在构建复杂应用程序时尤为有用,可以避免在最终镜像中包含不必要的构建工具和中间文件。
dockerfile
# 阶段1:使用 Node.js 构建应用程序
FROM node:14 as builder

WORKDIR /app

# 复制应用程序源代码到容器中
COPY . .

# 安装依赖并构建应用程序
RUN npm install
RUN npm run build

# 阶段2:创建最终镜像,只包含构建好的应用程序
FROM nginx:alpine

# 将阶段1中构建好的应用程序复制到最终镜像中
COPY --from=builder /app/dist /usr/share/nginx/html

# 非必须:如果需要自定义 Nginx 配置,可以将配置文件复制到镜像中
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 指定 Nginx 的工作目录
WORKDIR /usr/share/nginx/html

# 暴露端口
EXPOSE 80

# 容器启动时执行的命令
CMD ["nginx", "-g", "daemon off;"]
  • 阶段 1 (as builder): 使用 Node.js 镜像,将应用程序源代码复制到容器中,安装依赖并构建应用程序。这个阶段产生一个包含构建好的应用程序的中间镜像。
  • 阶段 2: 使用 Nginx 镜像,只将构建好的应用程序从阶段 1 中的中间镜像复制到最终镜像中。这样最终的镜像中只包含运行应用程序所需的最小文件和依赖。

INFO

在使用多阶段构建时,你并不局限于从之前在 Dockerfile 中创建的阶段中复制。你可以使用 COPY --from 指令从单独的镜像复制,可以使用本地镜像名称、本地或 Docker 注册表上的标签或标签 ID。

dockerfile
COPY --from=nginx:1.25.3-alpine /etc/nginx/nginx.conf /nginx.conf
相关文件
dockerfile
FROM golang:1.21-alpine as base
WORKDIR /src
COPY main.go /src/main.go
RUN go build -o /bin/hello ./main.go

FROM alpine:3.18 as stage1
RUN echo "Hello, stage1"

FROM  as stage2
COPY --from=base /bin/hello /bin/hello
CMD ["/bin/hello"]
go
package main

import "fmt"

func main() {
  fmt.Println("hello, world")
}
bash
 DOCKER_BUILDKIT=1 dkb --no-cache -f Dockerfile-multi-stage --target stage2 -t multi-stage-buildkit . # 启用 BuildKit 后,在此 Dockerfile 中构建 stage2 目标,意味着只处理 base 和 stage2 。对 stage1 没有依赖性,所以跳过了它。
[+] Building 5.4s (10/10) FINISHED                                                                                    docker:default
 => [internal] load .dockerignore                                                                                               0.0s
 => => transferring context: 2B                                                                                                 0.0s
 => [internal] load build definition from Dockerfile-multi-stage                                                                0.0s
 => => transferring dockerfile: 295B                                                                                            0.0s
 => [internal] load metadata for docker.io/library/golang:1.21-alpine                                                           0.8s
 => [base 1/4] FROM docker.io/library/golang:1.21-alpine@sha256:110b07af87238fbdc5f1df52b00927cf58ce3de358eeeb1854f10a8b5e5e14  0.0s
 => CACHED [base 2/4] WORKDIR /src                                                                                              0.0s
 => [internal] load build context                                                                                               0.0s
 => => transferring context: 108B                                                                                               0.0s
 => [base 3/4] COPY main.go /src/main.go                                                                                        0.1s
 => [base 4/4] RUN go build -o /bin/hello ./main.go                                                                             4.3s
 => [stage2 1/1] COPY --from=base /bin/hello /bin/hello                                                                         0.1s
 => exporting to image                                                                                                          0.0s
 => => exporting layers                                                                                                         0.0s
 => => writing image sha256:898867bfef2b7128f729132d1486ee8c38a47fc9d4daf82dbdad826e4f25af21                                    0.0s
 => => naming to docker.io/library/multi-stage-buildkit                                                                         0.0s
 DOCKER_BUILDKIT=0 dkb --no-cache -f Dockerfile-multi-stage --target stage2 -t multi-stage . # 在不使用 BuildKit 的情况下构建同一目标时,所有阶段都会被处理
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
            BuildKit is currently disabled; enable it by removing the DOCKER_BUILDKIT=0
            environment-variable.

Sending build context to Docker daemon  10.75kB
Step 1/9 : FROM golang:1.21-alpine as base
1.21-alpine: Pulling from library/golang
96526aa774ef: Already exists
834bccaa730c: Already exists
6bde6e5b7857: Already exists
8ebe3be3b56e: Already exists
Digest: sha256:110b07af87238fbdc5f1df52b00927cf58ce3de358eeeb1854f10a8b5e5e1411
Status: Downloaded newer image for golang:1.21-alpine
 ---> bb520cee46ae
Step 2/9 : WORKDIR /src
 ---> Running in 50f10e5d4a70
Removing intermediate container 50f10e5d4a70
 ---> 89add9eafb7c
Step 3/9 : COPY main.go /src/main.go
 ---> cd7e56989a8b
Step 4/9 : RUN go build -o /bin/hello ./main.go
 ---> Running in dca9ddcf1099
Removing intermediate container dca9ddcf1099
 ---> 091a4d7d5cb7
Step 5/9 : FROM alpine:3.18 as stage1
3.18: Pulling from library/alpine
96526aa774ef: Already exists
Digest: sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978
Status: Downloaded newer image for alpine:3.18
 ---> 8ca4688f4f35
Step 6/9 : RUN echo "Hello, stage1"
 ---> Running in efe5def848eb
Hello, stage1
Removing intermediate container efe5def848eb
 ---> 0cfb00902193
Step 7/9 : FROM scratch as stage2
 --->
Step 8/9 : COPY --from=base /bin/hello /bin/hello
 ---> 47ffe873ac7b
Step 9/9 : CMD ["/bin/hello"]
 ---> Running in a064bc18979d
Removing intermediate container a064bc18979d
 ---> eb7aef71c12b
Successfully built eb7aef71c12b
Successfully tagged multi-stage:latest

ARG 构建镜像时传递参数

  • ARG(Argument)指令用于定义构建时传递给镜像的参数(在构建时通过 --build-arg 选项传递)。
  • 使用 ARG 指令可以增加 Docker 镜像构建的灵活性,允许在构建过程中传递一些参数,从而根据需要进行定制化。
dockerfile
# 使用 ARG 定义一个参数
ARG BASE_IMAGE=alpine:latest

# 使用 FROM 指定基础镜像,这里使用了 ARG 定义的参数
FROM ${BASE_IMAGE}

# 在构建过程中可以使用 ARG 定义的参数
ARG APP_VERSION=1.0

# 其他构建步骤
# ...

# 在构建过程中使用 ARG 定义的参数
LABEL version=${APP_VERSION}

# 其他构建步骤
# ...

FROM ${BASE_IMAGE} 中,${BASE_IMAGE} 使用了该参数,允许在构建时通过 --build-arg 选项传递不同的基础镜像。

bash
docker build --build-arg BASE_IMAGE=ubuntu:latest -t my-custom-image .

DANGER

  • 不建议使用构建时变量传递 GitHub 密钥、用户凭证等秘密。因为使用镜像的用户可以使用 docker history 命令看到构建时变量的值。
  • 请参阅 RUN --mount=type=secret 部分,了解在构建图像时使用秘密的安全方法。
bash
 cat Dockerfile-arg
FROM alpine:3.18 as base

CMD ["/bin/bash"]

ARG OUTPUT_STRING="Hello World"

RUN echo "${OUTPUT_STRING}"
 docker build --build-arg OUTPUT_STRING="hello ryan~" -t arg-test -f Dockerfile-arg .
[+] Building 1.6s (6/6) FINISHED                                                                                      docker:default
 => [internal] load build definition from Dockerfile-arg                                                                        0.0s
 => => transferring dockerfile: 147B                                                                                            0.0s
 => [internal] load .dockerignore                                                                                               0.0s
 => => transferring context: 2B                                                                                                 0.0s
 => [internal] load metadata for docker.io/library/alpine:3.18                                                                  1.1s
 => [1/2] FROM docker.io/library/alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978            0.1s
 => => resolve docker.io/library/alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978            0.0s
 => => sha256:48d9183eb12a05c99bcc0bf44a003607b8e941e1d4f41f9ad12bdcc4b5672f86 528B / 528B                                      0.0s
 => => sha256:8ca4688f4f356596b5ae539337c9941abc78eda10021d35cbc52659c74d9b443 1.47kB / 1.47kB                                  0.0s
 => => sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978 1.64kB / 1.64kB                                  0.0s
 => [2/2] RUN echo "hello ryan~"                                                                                                0.3s
 => exporting to image                                                                                                          0.1s
 => => exporting layers                                                                                                         0.0s
 => => writing image sha256:23aa03674aec5fd41c71471700fb1b5a7f6bfa53e4a6417a2bfbe3f5382bd63f                                    0.0s
 => => naming to docker.io/library/arg-test                                                                                     0.0s
 docker history arg-test
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
23aa03674aec   14 seconds ago   RUN |1 OUTPUT_STRING=hello ryan~ /bin/sh -c   0B       buildkit.dockerfile.v0
<missing>      14 seconds ago   ARG OUTPUT_STRING=Hello World                    0B       buildkit.dockerfile.v0
<missing>      14 seconds ago   CMD ["/bin/bash"]                                0B       buildkit.dockerfile.v0
<missing>      7 weeks ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]               0B
<missing>      7 weeks ago      /bin/sh -c #(nop) ADD file:756183bba9c7f4593…   7.34MB

在 Docker 中,CMDENTRYPOINT 是两个用于定义容器启动命令的关键指令。它们可以一起使用,也可以单独使用,取决于你的需求。下面是对它们的详细解释:

CMD 容器启动默认执行的命令

CMD | Docker Docs

CMD 指令用于定义容器启动时默认执行的命令。它有两种不同的形式:

  • Shell 形式

    dockerfile
    CMD command param1 param2

    这种形式使用默认的 shell 执行命令。例如:

    dockerfile
    CMD echo "Hello, World!"

    这将在容器启动时执行 echo "Hello, World!"

  • Exec 形式

    dockerfile
    CMD ["executable","param1","param2"]

    这种形式直接执行指定的可执行文件,不使用默认的 shell。例如:

    dockerfile
    CMD ["/bin/bash", "-c", "echo Hello, World!"]

    这会以非交互的方式执行 Bash,并输出 "Hello, World!"。

CMD 指令可以被 Dockerfile 中的最后一个有效 CMD 指令所覆盖。如果用户在运行容器时指定了命令,它将替换 CMD 中的默认命令。

ENTRYPOINT 容器启动时执行的命令

ENTRYPOINT | Docker Docs

ENTRYPOINT 指令用于配置容器启动时执行的命令。它也有两种形式:

  • Shell 形式

    dockerfile
    ENTRYPOINT command param1 param2

    这种形式使用默认的 shell 执行命令。

  • Exec 形式

    dockerfile
    ENTRYPOINT ["executable", "param1", "param2"]

    这种形式直接执行指定的可执行文件,不使用默认的 shell。

ENTRYPOINT 指令的一个关键特性是,它可以与 CMD 指令结合使用。如果定义了 ENTRYPOINT,那么 CMD 的内容会被传递给 ENTRYPOINT 作为参数。这种组合使得你可以定义一个容器的主要执行命令,同时保留一些默认参数。

CMD 与 ENTRYPOINT 结合使用

在 Dockerfile 中,你可以同时定义 ENTRYPOINTCMD,这样它们就会形成一个命令的基础。CMD 提供默认参数,而 ENTRYPOINT 提供主要的执行命令。例如:

dockerfile
FROM ubuntu:latest
ENTRYPOINT ["echo", "Hello,"]
CMD ["World!"]

在这个例子中,当你运行容器时,它会执行 echo Hello, World!。如果你在运行容器时指定了额外的命令,它们将替换 CMD 中的默认参数。

bash
docker run my-image Hi there!

上面的命令将输出:Hello, Hi there!,因为 "Hi there!" 替换了默认的 "World!"。

总体而言,CMDENTRYPOINT 的结合使用为容器提供了灵活性,允许用户在运行容器时指定不同的参数,同时保留了容器的主要执行命令。

ENTRYPOINT 和 CMD 的区别

ENTRYPOINTCMD 都是 Dockerfile 中用于定义容器启动命令的指令,它们之间有一些关键的区别:

  • ENTRYPOINT

    • 主要执行命令ENTRYPOINT 用于配置容器启动时执行的主要命令。它可以是一个可执行文件或者一个 shell 命令。
    • 不会被覆盖ENTRYPOINT 定义的命令不会被 docker run 命令中指定的命令参数覆盖。它始终是容器启动时执行的主要命令。
  • CMD

    • 提供默认参数CMD 用于为容器提供默认的执行参数。它可以包含可执行文件及其参数,或者是一个 shell 命令。

    • 可以被覆盖CMD 中定义的命令可以被 docker run 命令中指定的命令参数覆盖。如果用户在运行容器时提供了命令参数,它们将替换 CMD 中定义的默认参数。

      dockerfile
      CMD ["echo", "World!"]

      如果运行容器时提供额外的参数,它们将替换 CMD 中的默认参数。

      bash
      docker run my-image Hi there!

      这会输出:Hi there!

    • 可被覆盖或扩展CMD 可以被 Dockerfile 中的最后一个有效 CMD 指令所覆盖,也可以被 docker run 命令中的参数扩展。

bash
 cat Dockerfile-ENTRYPOINT
FROM alpine:3.18

ENTRYPOINT [ "echo", "Ryan" ]

CMD [ "666~" ]%
 docker build -f Dockerfile-ENTRYPOINT -t entrypoint-cmd .
[+] Building 0.9s (5/5) FINISHED                                                                                      docker:default
 => [internal] load .dockerignore                                                                                               0.0s
 => => transferring context: 2B                                                                                                 0.0s
 => [internal] load build definition from Dockerfile-ENTRYPOINT                                                                 0.0s
 => => transferring dockerfile: 115B                                                                                            0.0s
 => [internal] load metadata for docker.io/library/alpine:3.18                                                                  0.7s
 => CACHED [1/1] FROM docker.io/library/alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978     0.0s
 => exporting to image                                                                                                          0.0s
 => => exporting layers                                                                                                         0.0s
 => => writing image sha256:a9114a4a3719ed5cdf7b097f338e3a5be112e52644ecc42c527f0f2231530311                                    0.0s
 => => naming to docker.io/library/entrypoint-cmd                                                                               0.0s
 docker run entrypoint-cmd:latest
Ryan 666~
 docker run entrypoint-cmd:latest 888~
Ryan 888~

COPYADD 是 Dockerfile 中用于将文件或目录从主机复制到容器中的两个指令。它们之间有一些区别,下面是详细解释:

COPY 复制宿主机文件到容器内

COPY | Docker Docs

COPY 指令用于将文件或目录从构建上下文的主机系统复制到 Docker 镜像中。语法如下:

dockerfile
COPY <源路径>... <目标路径>

# 将主机上当前目录下的 app 文件夹复制到容器中的 /app 目录
COPY ./app /app
  • <源路径>:是主机上的文件或目录的路径,可以是相对路径或绝对路径。
  • <目标路径>:是容器中的目标路径,表示文件或目录将被复制到容器的哪个位置。

ADD 复制宿主机文件到容器内

ADD | Docker Docs

ADD 指令的功能与 COPY 类似,但具有更多的功能。除了复制文件,ADD 还可以自动解压缩压缩文件、从 URL 复制文件,并具有一些其他特性。语法如下:

dockerfile
ADD <源路径>... <目标路径>

# 将主机上当前目录下的 app 文件夹复制到容器中的 /app 目录
ADD ./app /app

COPY 相同,<源路径> 是主机上的文件或目录的路径,而 <目标路径> 是容器中的目标路径。

COPY 和 ADD

  • 功能差异

    • COPY 专注于将本地文件系统上的文件或目录复制到容器中。
    • ADD 不仅可以复制文件,还具有一些扩展功能,如解压缩压缩文件、从 URL 复制文件等。
  • 建议使用 COPY

    • 通常情况下,推荐使用 COPY,因为它更明确,功能更单一。如果只需要复制文件,COPY 是更好的选择。
  • 缓存问题

    • 在构建过程中,Docker 使用构建缓存来避免重复下载或复制相同的文件。如果文件没有发生变化,COPYADD 指令都会使用缓存。但是,由于 ADD 具有更多功能,可能在某些情况下导致不必要的缓存失效,因此在一般情况下,COPY 更容易控制和预测。
  • 解压缩

    • ADD 在复制压缩文件时会自动解压,而 COPY 不会自动解压。如果你的需求是将文件复制到容器中而不解压,可以使用 COPY

总体而言,对于普通的文件复制操作,推荐使用 COPY,而对于具有特殊需求的情况,可以考虑使用 ADD

bash
 tar -czvf demo.tar.gz demo
demo/
demo/demo1.txt
demo/demo2.txt
demo/demo4.txt
demo/demo5.txt
demo/demo3.txt
 cat Dockerfile-COPY-ADD
FROM alpine:3.18

# install tree
# sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.cloud.tencent.com/g' /etc/apk/repositories
RUN apk add --no-cache tree

RUN mkdir /add-dir /copy-dir

COPY demo.tar.gz /copy-dir/

ADD demo.tar.gz /add-dir/

CMD ["tree", "/add-dir", "/copy-dir"]
 dk build -t copy-add-demo -f Dockerfile-COPY-ADD .
[+] Building 3.1s (11/11) FINISHED                                                                                    docker:default
 => [internal] load build definition from Dockerfile-COPY-ADD                                                                   0.0s
 => => transferring dockerfile: 423B                                                                                            0.0s
 => [internal] load .dockerignore                                                                                               0.0s
 => => transferring context: 2B                                                                                                 0.0s
 => [internal] load metadata for docker.io/library/alpine:3.18                                                                  0.7s
 => CACHED [1/6] FROM docker.io/library/alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978     0.0s
 => [internal] load build context                                                                                               0.0s
 => => transferring context: 33B                                                                                                0.0s
 => [2/6] RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.cloud.tencent.com/g' /etc/apk/repositories                               0.2s
 => [3/6] RUN apk add --no-cache tree                                                                                           1.4s
 => [4/6] RUN mkdir /add-dir /copy-dir                                                                                          0.4s
 => [5/6] COPY demo.tar.gz /copy-dir/                                                                                           0.1s
 => [6/6] ADD demo.tar.gz /add-dir/                                                                                             0.1s
 => exporting to image                                                                                                          0.2s
 => => exporting layers                                                                                                         0.2s
 => => writing image sha256:d4bdc32841fc21bd12067a8d3993c0e4ffcc129a22f105e4aebb380e59e5ceda                                    0.0s
 => => naming to docker.io/library/copy-add-demo                                                                                0.0s
 docker run --name copy-add-demo copy-add-demo:latest # 可以看到 COPY 的文件没有解压,而 ADD 的文件解压了
/add-dir
└── demo
    ├── demo1.txt
    ├── demo2.txt
    ├── demo3.txt
    ├── demo4.txt
    └── demo5.txt
/copy-dir
└── demo.tar.gz

3 directories, 6 files